use crate::{error::*, utils::file::file_name};
use arc_swap::ArcSwapOption;
use log::debug;
use serde::{Deserialize, Serialize};
use std::{
    fs,
    sync::{Arc, LazyLock},
};

#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct ErrorLogConf {
    pub enabled: bool,
    pub console: bool,
    pub level: String,
    pub file_path: String,
    pub buf_size: usize,
    pub max_pending_bufs: usize,
    pub flush_interval_ms: u64,
    pub rolling: String,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct LinkerConf {
    // Tokio runtime threads for main runtime.
    // Default 1.
    pub main_threads: usize,

    // Tokio runtime threads for worker runtime.
    // Default 1.
    pub worker_threads: usize,

    // Set CPU Affinity for worker runtime threads.
    // Default None.
    pub worker_cpu_affinity: Option<Vec<usize>>,

    // Timeout period for graceful shutdown.
    // Default 5m
    pub graceful_shutdown_timeout_ms: usize,

    pub listeners_conf: String,

    pub http_main_conf: String,

    pub error_log: ErrorLogConf,
}

impl Default for LinkerConf {
    fn default() -> Self {
        LinkerConf {
            main_threads: 1,
            worker_threads: 1,
            worker_cpu_affinity: None,
            graceful_shutdown_timeout_ms: 5 * 60 * 1000,
            listeners_conf: "".to_string(),
            http_main_conf: "".to_string(),
            error_log: ErrorLogConf::default(),
        }
    }
}

#[allow(dead_code)]
impl LinkerConf {
    pub fn load_from_yaml(path: &str) -> Result<Self> {
        let conf_str = fs::read_to_string(path).if_err(
            ErrorType::ParseError,
            format!("Unable to read conf file from {path}"),
        )?;

        debug!("LinkerConf file read from {} success", file_name(path));
        Self::from_yaml(&conf_str)
    }

    pub fn from_yaml(conf_str: &str) -> Result<Self> {
        let conf: LinkerConf = serde_yaml::from_str(conf_str)
            .if_err(ErrorType::ParseError, "Unable to parse linker conf")?;
        conf.validate()
    }

    fn validate(self) -> Result<Self> {
        if self.main_threads < 1 {
            return Error::e_explain(
                ErrorType::InvalidError,
                "main_threads must be greater than or equal to 1",
            );
        }

        if self.worker_threads < 1 {
            return Error::e_explain(
                ErrorType::InvalidError,
                "worker_threads must be greater than or equal to 1",
            );
        }

        if self.listeners_conf.is_empty()
            || !self.listeners_conf.starts_with('/')
        {
            return Error::e_explain(
                ErrorType::InvalidError,
                "listeners_conf file path must be an absolute path",
            );
        }

        if self.http_main_conf.is_empty()
            || !self.http_main_conf.starts_with('/')
        {
            return Error::e_explain(
                ErrorType::InvalidError,
                "http_main_conf file path must be an absolute path",
            );
        }

        if self.error_log.enabled {
            let err_log_path = &self.error_log.file_path;
            if err_log_path.is_empty() || !err_log_path.starts_with('/') {
                return Error::e_explain(
                    ErrorType::InvalidError,
                    "error_log file path must be an absolute path",
                );
            }
        }

        Ok(self)
    }
}

static LINKER_CONF: LazyLock<ArcSwapOption<LinkerConf>> =
    LazyLock::new(|| ArcSwapOption::new(None));

pub fn set_linker_conf(conf: LinkerConf) {
    LINKER_CONF.store(Some(Arc::new(conf)));
}

pub fn get_linker_conf() -> Arc<LinkerConf> {
    LINKER_CONF.load().clone().unwrap()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_linker_conf_parse() {
        let conf_str = r#"
            worker_threads: 4
            worker_cpu_affinity:
                - 0
                - 1
                - 2
                - 3
            listeners_conf: "/opt/bdcdn/linker/conf/listeners.yaml"
            http_main_conf: "/opt/bdcdn/linker/conf/http/main.yaml"
            graceful_shutdown_timeout_ms: 600000
        "#;

        let linker_conf = LinkerConf::from_yaml(conf_str).unwrap();
        println!("{:#?}", linker_conf);

        assert_eq!(linker_conf.main_threads, 1);
        assert_eq!(linker_conf.worker_threads, 4);
        assert_eq!(linker_conf.graceful_shutdown_timeout_ms, 10 * 60 * 1000);

        let affinity = linker_conf.worker_cpu_affinity.unwrap();
        assert_eq!(affinity.len(), 4);
        assert_eq!(*affinity.get(0).unwrap(), 0);
        assert_eq!(*affinity.get(1).unwrap(), 1);
        assert_eq!(*affinity.get(2).unwrap(), 2);
        assert_eq!(*affinity.get(3).unwrap(), 3);
    }
}
